package xfuzzy.xfghl.view;

import java.awt.*;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.event.*;
import java.io.File;

import xfuzzy.Xfuzzy;
import xfuzzy.XmlParser;
import xfuzzy.lang.Specification;
import xfuzzy.util.XConstants;
import xfuzzy.util.XDialog;
import xfuzzy.util.XFileChooser;
import xfuzzy.util.XFileFilter;
import xfuzzy.util.XMenu;
import xfuzzy.xfghl.algorithm.GeneticAlgorithm;
import xfuzzy.xfghl.algorithm.ResultGA;
import xfuzzy.xfghl.model.XfghlConfig;
import xfuzzy.xfghl.model.XfghlControllerNew;
import xfuzzy.xfhl.view.XfhlDataView;

import java.util.Observable;
import java.util.Observer;
import javax.swing.JMenuBar;

/**
 * La clase principal de la vista de la herramienta Xfghl
 * 
 * @author Alberto David Fernandez Fernandez
 * 
 */

public class XfghlViewNew extends JFrame implements Observer {

	private static final long serialVersionUID = 1L;

	/**
	 * Panel que contiene toda el JFrame
	 */
	private JPanel jContentPane;

	/**
	 * Panel donde se muestra la mejor estructura
	 */
	private JPanel jPnlBest = null;

	/**
	 * Configuracion de la aplicacion XfhlConfig
	 */
	private XfghlConfig xfghlConfig; 

	/**
	 * Hilo para la ejecucion del algoritmo genetico
	 * */
	private Thread geneticAlgorithmThread;
	
	/**
	 * Instancia del algoritmo genetico
	 * */
	private GeneticAlgorithm geneticAlgorithm;

	/**
	 * Instancia al mejor XfhlDataView
	 */
	private XfhlDataView bestDataView;

	/**
	 * Instancia al controlador
	 */
	private XfghlControllerNew xfhlControllerView;

	/**
	 * Menu file
	 */
	private XMenu[] menu;

	/**
	 * Ventana principal de xfuzzy
	 */
	private Xfuzzy xfuzzy;

	/**
	 * El sistema difuso donde guardar el resultado
	 */
	private Specification original;

	/**
	 * Boton ejecutar
	 */
	private JButton jBtnRun = null;

	/**
	 * Boton cerrar
	 */
	private JButton jBtnClose = null;

	/**
	 * Boton recargar
	 */
	private JButton jBtnReload = null;

	/**
	 * Boton aplicar
	 */
	private JButton jBtnApply = null;

	/**
	 * Boton guardar
	 */
	private JButton jBtnSave = null;

	/**
	 * Numero de evaluaciones realizadas
	 * */
	private JLabel lblNumEvaluationsText;
	private JLabel lblNumEvaluationsValue;
	
	/**
	 * Condicion de parada seleccionada
	 * */
	private JLabel lblStopConditionText;
	private JLabel lblStopConditionValue;
	
	/**
	 * Error del mejor individuo evaluado
	 * */
	private JLabel lblErrorText;
	private JLabel lblErrorValue;
	
	/**
	 * Panel con la representacion de la evolucion del algoritmo genetico
	 * */
	private GeneticDataGraphPanel dataPanel;

	/**
	 * Notificacion de sucesos del algoritmo
	 * */
	private JLabel lblNotification;
	
	/**
	 * Constructor de la clase
	 * @param Instancia al controlador
	 * @param xfuzzy Instancia a la ventana principal de Xfuzzy
	 * @param original Especificacion original
	 */
	public XfghlViewNew(XfghlControllerNew xfghlControllerView, Xfuzzy xfuzzy, Specification original) {
		super();
		this.xfhlControllerView = xfghlControllerView;
		this.xfuzzy = xfuzzy;
		this.original = original;
		initialize();
		this.setVisible(true);
	}

	/**
	 * Metodo que inicia el proceso
	 * 
	 * @param xfhlProcess
	 *            Instancia al xhlProcess
	 * @param currentDataView
	 *            Array de objetos XfhlDataView para ver los graficos
	 */
	public void run() {
		
		enabledButton(1, true); 	// Reload
		enabledButton(2, true); 	// Apply
		enabledButton(3, true); 	// Save
		enabledButton(4, false); 	// Close

		this.jContentPane.revalidate();
		this.menu[0].setEnabled(false); // desabilitamos el menu entero

		// se crea el hilo para el algoritmo genetico y se lanza
		this.geneticAlgorithm = new GeneticAlgorithm();
		this.geneticAlgorithm.configure(this.xfghlConfig);
		int stopCondition = this.xfghlConfig.getStopConditionGA();
		int stopValue = this.xfghlConfig.getValueStopConditionGA();
		if (stopCondition == XfghlProperties.stopConditionEvaluationsValue)  {
			this.lblStopConditionValue.setText(String.valueOf(stopValue) + " evaluations");
		}
		this.dataPanel.update(this.xfghlConfig.getValueStopConditionGA());
		
		this.geneticAlgorithm.addObserver(this);
		this.geneticAlgorithmThread = new Thread(geneticAlgorithm);
		this.geneticAlgorithmThread.start();
		
		this.jBtnRun.setText("Pause");
		this.jBtnRun.setActionCommand("Pause");
		this.lblNotification.setText("Running...");
	}

	/**
	 * Hablilita un boton del menu
	 * 
	 * @param ind
	 *            indice del boton a habilitar 0 RUN/PAUSE/CONTINUE 1 RELOAD 2
	 *            APPLY 3 SAVE 4 CLOSE
	 */
	private void enabledButton(int ind, boolean value) {
		switch (ind) {
			case 0:
				this.jBtnRun.setEnabled(value);
				break;
			case 1:
				this.jBtnReload.setEnabled(value);
				break;
			case 2:
				this.jBtnApply.setEnabled(value);
				break;
			case 3:
				this.jBtnSave.setEnabled(value);
				break;
			case 4:

				this.jBtnClose.setEnabled(value);
				break;
			default:
				break;
		}
	}

	/**
	 * Hablilita o deshabilita un boton del menu
	 * 
	 * @param men
	 *            0 FILE
	 * @param ind
	 *            Opcion del menu 0 New Configuration 1 Edit Configuration 2
	 *            Load Configuration 3 Save Configuration
	 */
	private void enabledOptionMenu(int men, int ind, boolean value) {
		this.menu[men].getItem(ind).setEnabled(value);
	}

	/**
	 * Metodo que se crea el jframe y el menu
	 * 
	 * @return void
	 */
	private void initialize() {
		
		this.setSize(900, 731);
		this.setLocationRelativeTo(null);
		this.setResizable(false);
		this.setTitle("Xfghl");
		this.setFont(XConstants.font);
		
		String title[] = { "Configuration" };
		String label[][] = { { "New           ", "Edit          ", "Load          ", "Save          " } };
		String command[][] = { { "NewConfiguration", "EditConfiguration", "LoadConfiguration", "SaveConfiguration" }, };

		menu = new XMenu[1];
		for (int i = 0; i < menu.length; i++)
			 menu[i] = new XMenu(title[i], label[i], command[i], this.xfhlControllerView);

		// Deshabilitamos las opciones que no estan activas al principio
		// 1 -> Edit
		// 3 -> Save

		menu[0].getItem(1).setEnabled(false);
		menu[0].getItem(3).setEnabled(false);

		JMenuBar menubar = new JMenuBar();
		menubar.add(Box.createHorizontalStrut(5));
		menubar.add(menu[0]);
		menubar.add(Box.createHorizontalStrut(5));
		this.setJMenuBar(menubar);

		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent evt) {
				dispose();
			}
		});
		jContentPane = new JPanel();
		jContentPane.setLayout(null);
		jContentPane.setFont(XConstants.font);
		jContentPane.setSize(new Dimension(872, 663));
		
		jPnlBest = new JPanel(new BorderLayout());
		jPnlBest.setBounds(new Rectangle(30, 15, 840, 251));
		jPnlBest.setBorder(BorderFactory.createTitledBorder(null, "Best found", TitledBorder.CENTER,
				TitledBorder.DEFAULT_POSITION, XConstants.font, new Color(51, 51, 51)));
		jContentPane.add(jPnlBest, null);
		
		jBtnRun = new JButton();
		jBtnRun.setBounds(new Rectangle(75, 629, 120, 21));
		jBtnRun.setActionCommand("Run");
		jBtnRun.setText("Run");
		jBtnRun.setBorder(BorderFactory.createRaisedBevelBorder());
		jBtnRun.setHorizontalAlignment(JButton.CENTER);
		jBtnRun.setFont(XConstants.font);
		jBtnRun.addActionListener(this.xfhlControllerView);
		jBtnRun.setEnabled(false);
		jContentPane.add(jBtnRun, null);
		
		jBtnClose = new JButton();
		jBtnClose.setBounds(new Rectangle(674, 629, 120, 21));
		jBtnClose.setText("Close");
		jBtnClose.setActionCommand("Close");
		jBtnClose.setBorder(BorderFactory.createRaisedBevelBorder());
		jBtnClose.setHorizontalAlignment(JButton.CENTER);
		jBtnClose.setFont(XConstants.font);
		jBtnClose.addActionListener(this.xfhlControllerView);
		jContentPane.add(jBtnClose, null);
		
		jBtnReload = new JButton();
		jBtnReload.setBounds(new Rectangle(226, 630, 120, 21));
		jBtnReload.setText("Reload");
		jBtnReload.setActionCommand("Reload");
		jBtnReload.setBorder(BorderFactory.createRaisedBevelBorder());
		jBtnReload.setHorizontalAlignment(JButton.CENTER);
		jBtnReload.setFont(XConstants.font);
		jBtnReload.addActionListener(this.xfhlControllerView);
		jBtnReload.setEnabled(false);
		jContentPane.add(jBtnReload, null);
		
		jBtnApply = new JButton();
		jBtnApply.setBounds(new Rectangle(375, 630, 120, 21));
		jBtnApply.setText("Apply");
		jBtnApply.setActionCommand("Apply");
		jBtnApply.setBorder(BorderFactory.createRaisedBevelBorder());
		jBtnApply.setHorizontalAlignment(JButton.CENTER);
		jBtnApply.setFont(XConstants.font);
		jBtnApply.addActionListener(xfhlControllerView);
		jBtnApply.setEnabled(false);
		jContentPane.add(jBtnApply, null);
		
		jBtnSave = new JButton();
		jBtnSave.setBounds(new Rectangle(525, 629, 120, 21));
		jBtnSave.setText("Save");
		jBtnSave.setActionCommand("Save");
		jBtnSave.setBorder(BorderFactory.createRaisedBevelBorder());
		jBtnSave.setHorizontalAlignment(JButton.CENTER);
		jBtnSave.setFont(XConstants.font);
		jBtnSave.addActionListener(this.xfhlControllerView);
		jBtnSave.setEnabled(false);
		jContentPane.add(jBtnSave, null);
		
		lblNumEvaluationsText = new JLabel("Evaluations");
		lblNumEvaluationsText.setBounds(704, 300, 166, 15);
		jContentPane.add(lblNumEvaluationsText);
		
		lblNumEvaluationsValue = new JLabel("0");
		lblNumEvaluationsValue.setHorizontalAlignment(SwingConstants.RIGHT);
		lblNumEvaluationsValue.setBounds(704, 327, 166, 15);
		jContentPane.add(lblNumEvaluationsValue);
		
		lblStopConditionText = new JLabel("Stop condition");
		lblStopConditionText.setBounds(704, 354, 166, 15);
		jContentPane.add(lblStopConditionText);
		
		lblStopConditionValue = new JLabel("0");
		lblStopConditionValue.setHorizontalAlignment(SwingConstants.RIGHT);
		lblStopConditionValue.setBounds(704, 381, 166, 15);
		jContentPane.add(lblStopConditionValue);
		
		lblErrorText = new JLabel("Error");
		lblErrorText.setBounds(704, 408, 166, 15);
		jContentPane.add(lblErrorText);
		
		lblErrorValue = new JLabel("0.0");
		lblErrorValue.setHorizontalAlignment(SwingConstants.RIGHT);
		lblErrorValue.setBounds(704, 435, 166, 15);
		jContentPane.add(lblErrorValue);
		
		dataPanel = new GeneticDataGraphPanel();
		dataPanel.setBounds(30, 300, 651, 317);
		jContentPane.add(dataPanel);

		this.setContentPane(jContentPane);
		
		lblNotification = new JLabel("");
		lblNotification.setBounds(704, 462, 166, 15);
		jContentPane.add(lblNotification);
	}

	/**
	 * Guarda la mejor epecificacion hasta el momento
	 */

	public void saveSpecification() {
		
		Specification spec = this.geneticAlgorithm.getBest().getSpecification();

		// Copiamos la mejor especificacion hasta el momento
		if (spec != null) {
			// Elegimos el nombre del fichero de datos
			XFileChooser fileDataSet = new XFileChooser(new File(""), XFileChooser.SAVE);
			fileDataSet.addFileFilter(new XFileFilter(".xfl", "System Specification File"));
			fileDataSet.showDialog();

			if (fileDataSet.getSelectedFile() != null) {
				String ruta = fileDataSet.getSelectedFile().getPath();
				if (!ruta.endsWith(".xfl"))
					ruta += ".xfl";
				File file = new File(ruta);

				if (spec.save_as(file)) {
					this.xfuzzy.load(file);
				}
			}
		} else {
			XDialog.showMessage(null, "Sorry, not exist any specification.");
		}
	}

	/**
	 * Metodo que actualiza la especificicacion actual por la seleccionada en la
	 * ventana principal de xfuzzy
	 */
	public void apply() {
		
		Specification spec = this.geneticAlgorithm.getBest().getSpecification();

		// Copiamos la mejor especificacion hasta el momento
		if (spec != null) {
			this.original.setOperatorsets(spec.getOperatorsets());
			this.original.setTypes(spec.getTypes());
			this.original.setRulebases(spec.getRulebases());
			this.original.setSystemModule(spec.getSystemModule());
			this.original.setModified(true);
		} 
		else {
			XDialog.showMessage(null, "Sorry, not exist any specification.");
		}
	}

	/**
	 * Guarda la configuracion de la herramienta
	 */
	public void saveConfiguration() {
		
		// Copiamos la mejor especificacion hasta el momento
		if (this.xfghlConfig != null) {
			// Elegimos el nombre del fichero de datos
			XFileChooser fileDataSet = new XFileChooser(new File(""), XFileChooser.SAVE);
			fileDataSet.addFileFilter(new XFileFilter(".xml", "System Configuration File"));
			fileDataSet.showDialog();

			if (fileDataSet.getSelectedFile() != null) {

				String ruta = fileDataSet.getSelectedFile().getPath();
				if (!ruta.endsWith(".xml"))
					ruta += ".xml";
				File file = new File(ruta);

				String[] message = { "The configuration has been saved:", ruta };

				if (this.xfghlConfig.save(file))
					XDialog.showMessage(null, message);
				else
					XDialog.showMessage(null, "The configuration has NOT been saved.");
			}
		} 
		else {
			XDialog.showMessage(null, "Sorry, not exist any configuration.");
		}
	}
	
	/**
	 * Carga una configuracion desde un fichero XML
	 * */

	public void loadConfiguration() {
		
		boolean exist = false;
		String ruta = "";
		do {
			XFileChooser fileDataSet = new XFileChooser(new File(""), XFileChooser.SELECT);
			fileDataSet.addFileFilter(new XFileFilter(".xml", "XML"));

			int sal = fileDataSet.showDialog();

			if (sal == XFileChooser.CANCELED) {
				exist = true;
			} 
			else if (fileDataSet.getSelectedFile() != null) {
				File file = new File(fileDataSet.getSelectedFile().getPath());

				if (file.exists() && file.isFile()) {
					exist = true;
					ruta = fileDataSet.getSelectedFile().getPath();
				} 
				else {
					XDialog.showMessage(null, "The xml file must exist.");
				}
			}
		} while (!exist);

		if (ruta.compareTo("") != 0) {
			XmlParser parse = new XmlParser();
			parse.parseXML(new File(ruta));
			XfghlConfig config = parse.getXfghlConfig();
			if (config != null) {
				this.setConfig(config);
			} 
			else {
				XDialog.showMessage(null, "ERROR!! The xml configuration is incorrect.");
			}
		}
	}

	/**
	 * Metodo que actualiza los datos que se muestran en la vista
	 * 
	 * @param obs
	 */
	private synchronized void updateSin(Observable obs) {
		
		if (obs instanceof GeneticAlgorithm)  {
			GeneticAlgorithm ga = (GeneticAlgorithm) obs;
			ResultGA result = ga.getResult();
			this.lblNumEvaluationsValue.setText(String.valueOf(result.getNumEvaluations()));
			this.lblErrorValue.setText(String.valueOf(result.getBest()));
			
			// se dibuja un nuevo punto en la grafica de evolucion del algoritmo
			double status = ga.getBest().getFitness();
			System.out.println("status " + status);
			dataPanel.addStatus(status);
			
			if (ga.isChangeBest())  {
				updateBestSpec();
			}
			
			if (ga.isFinished())  {
				this.finished();
				this.lblNotification.setText("Finished!");
				
			}
		}
	}

	/**
	 * Metodo que actualiza la vista
	 * 
	 */
	public void update(Observable arg0, Object arg1) {
		
		updateSin(arg0);
	}

	/**
	 * Lanza una ventana de nueva configuracion
	 */
	public void newConfiguration() {
		XfghlConfigViewNew config = new XfghlConfigViewNew(this);
		config.setVisible(true);
		this.setEnabled(false);
	}

	/**
	 * Lanza una ventana de configuracion para editar la configuracion existente
	 */
	public void editConfiguration() {
		XfghlConfigViewNew config = new XfghlConfigViewNew(this, this.xfghlConfig);
		config.setVisible(true);
		this.setEnabled(false);
	}

	/**
	 * Metodo actualiza la mejor estructura
	 * 
	 */
	private void updateBestSpec() {
		// mostramos el mejor hasta el momento
		this.bestDataView = new XfhlDataView(this.geneticAlgorithm.getBest().getSpecification());

		this.jPnlBest.removeAll();
		this.jPnlBest.add(this.bestDataView.getBox());
		this.jPnlBest.repaint();
		this.jPnlBest.revalidate();
	}

	/**
	 * Obtiene la configuracion actual
	 * 
	 * @return
	 */
	public XfghlConfig getConfig() {
		return this.xfghlConfig;
	}

	/**
	 * Establece la configuracion en el jFrame y habilita y deshabilita los
	 * botones
	 * 
	 * @param xfhlConfig
	 */
	public void setConfig(XfghlConfig xfghlConfig) {
		
		this.xfghlConfig = xfghlConfig;

		this.jContentPane.revalidate();

		// Comprobamos si tenemos que poner los botones activos
		if (xfghlConfig != null) {
			enabledButton(0, true); // Run
			enabledOptionMenu(0, 1, true); // Edit
			enabledOptionMenu(0, 3, true); // Save

		} else {
			enabledButton(0, true); // Run
			enabledOptionMenu(0, 1, false); // Edit
			enabledOptionMenu(0, 3, false); // Save
		}
	}

	/**
	 * Cuando se llega al 100%
	 */
	private void finished() {
		
		this.jBtnRun.setText("Run");
		this.jBtnRun.setActionCommand("Run");

		// Habilitamos botones y opciones del menu
		enabledButton(1, false); // Reload
		enabledButton(4, true); // Close
		this.menu[0].setEnabled(true); // Habilitamos el menu
	}

	/**
	 * Metodo que para todo el proceso, no podra ser reiniciado
	 */
	@SuppressWarnings("deprecation")
	public void reload() {
		
		//xfghlProcess.stop();
		this.geneticAlgorithmThread.stop();
		this.lblNotification.setText("Stopped!");
		// Actualizamos el texto de la barra de progreso
//		String aux = "!STOPPED! The process has been canceled.";
//		this.jPBarGeneral.setString(aux);
//		this.jPBarGeneral.setValue(this.jPBarGeneral.getMaximum());

		this.jBtnRun.setText("Run");
		this.jBtnRun.setActionCommand("Run");

		enabledButton(1, false); // Reload
		enabledButton(4, true); // Close

		menu[0].setEnabled(true); // Menu principal
	}

	/**
	 * Metodo para pausar el proceso
	 */
	@SuppressWarnings("deprecation")
	public void pause() {
		
		this.geneticAlgorithmThread.suspend();
		enabledButton(1, false); // Reload
		this.jBtnRun.setText("Continue");
		this.jBtnRun.setActionCommand("Continue");
		this.lblNotification.setText("Paused!");
	}

	/**
	 * Metodo para continuar el proceso
	 */
	@SuppressWarnings("deprecation")
	public void contin() {
		
		this.geneticAlgorithmThread.resume();
		this.jBtnRun.setText("Pause");
		this.jBtnRun.setActionCommand("Pause");
		this.lblNotification.setText("Running...");

		enabledButton(1, true); // Reload
	}

	/**
	 * Devuelve la especificacion original
	 * @return Specification Especificacion original
	 */

	public Specification getOriginalSpec() {
		return this.original;
	}
} 
